/* Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/*
  <file> uls_stream.c </file>
  <brief>
    Read from input file and dump tokens in binary/text stream.
    This file is part of ULS, Unified Lexical Scheme.
  </brief>
  <author>
    Stanley J. Hong <babel96@gmail.com>, April 2011.
  </author>
*/

#include "uls.h"

#include "common.h"
#include "sample_lex.h"
#ifndef WIN32
#include "fdfilter.h"
#endif

#ifdef WIN32
#define THIS_PROGNAME "UlsStream";
#else
#define THIS_PROGNAME "uls_stream";
#endif

char *progname;
int  opt_verbose, opt_write;
int  opt_want_eof;
int  opt_no_numbering;
char *config_name;
char *filelist;
char *input_file;
char *output_file;
int  out_ftype;
char *target_dir;
char *cmdline_filter;
char *tag_name;

char home_dir[1024];
char ulc_specname[1024];

uls_lex_t *sample_lex;

#ifdef HAVE_GETOPT_H
#include <getopt.h>

static const struct option longopts[] = {
	{ "output",  required_argument,        NULL, 'o' },
	{ "type",  required_argument,          NULL, 't' },
	{ "lang",  required_argument,          NULL, 'L' },
	{ "conglomerate", required_argument,   NULL, 'C' },
	{ "filter", required_argument,         NULL, 'f' },
	{ "name", required_argument,           NULL, 'n' },
	{ "no-linenum", no_argument,           NULL, 'x' },
	{ "want-eof", no_argument,             NULL, 'z' },
	{ "verbose", no_argument,              NULL, 'v' },
	{ "version", no_argument,              NULL, 'V' },
	{ "help",    no_argument,              NULL, 'h' },
	{ NULL, 0, NULL, 0 }
};
#endif

static char*
skip_space(char* lptr)
{
	register int ch;

	for ( ; (ch=*lptr)==' ' || ch=='\t'; lptr++)
		/* nothing */;
	return lptr;
}

static int
is_absolute_path(char* path)
{
	int stat;
#if defined(WIN32)
	if (strchr(path, ':') != NULL)  stat = 1;
	else stat = 0;
#else
	if (*path == '/') stat = 1;
	else stat = 0;
#endif
	return stat;
}

static void usage(void)
{
	printf("Usage: %s [OPTIONS]  <file|dir>\n\n", progname);
	printf("  or:  %s -L <ulc-file> <source-file|(*.uls)>\n\n", progname);
	printf("  or:  %s -L <ulc-file> -t <out-type> [-o <output-file>] <source-file|(*.uls)>\n\n", progname);
	printf("  or:  %s  <uls-file|(*.uls)>\n\n", progname);
	printf("  or:  %s [-L <ulc-file>] -C <list-file> <target-dir>\n\n", progname);
	printf("  or:  %s [-L <ulc-file>] -C <list-file> -f 'filter-path' <target-dir>\n\n", progname);

	printf("%s reads source file or lexical stream file(*.uls), a binary token stream file, and displays it or converts it into uls-file.\n", progname);
	printf("First, it reads the lexical configuration from ulc-file(*.ulc) specified by -L-option.\n");
	printf("Then, the lexical analyzing object reads 'input-file' which can be source file or uls-file.\n");
	printf("The output data of 'input-file' is a sequence of tokens with its lexeme as below.\n\n");

	printf("    [     ID]  DDD\n");
	printf("    [     <=]  <=\n");
	printf("    [     ID]  EEE\n");
	printf("    [      {]  {\n");
	printf("    [     ID]  YYY\n");
	printf("    [      =]  \n");
	printf("\n");

	printf("If you specify the -t-option, %s will produce a formatted binary file of the sequence suffiexed by '.uls'.\n",
		progname);
	printf("Then, again you can input the uls-file as 'input-file' to '%s.\n", progname);
	printf("In short, the input file of %s can be source-code file or uls-file.\n");
	printf("The available values for -t-option are 'bin'(host byteorder), 'bin-le'(little-endian) or 'bin-be'(big-endian).\n");
	printf("If you specify -C-option, you can get a conglomerate file of all the files in <list-file> ");
	printf("where the file in each line starts from <target-dir>.\n");
	printf("The <list-file> is just a simple list of file paths in 'target-dir' that you want to process.\n");
	printf("Let the <list-file> be as follows.\n");
	printf("     # Comment here\n\n");
	printf("     # A filepath per line\n\n");
	printf("     input1.c\n\n");
	printf("     src/input2.c\n\n");
	printf("     srclib/input3.c\n\n");
	printf("\n");

	printf("The output-file ('a.uls') is a sequence of tokens of all the files.\n");
	printf("The -f-option can be used with -C-option to filter the files before lexical analyzing.\n");
	printf("The argument of -f-option can be any command which inputs from stdin and outputs to stdout like 'gcc -E'.\n");
	printf("\n");

	printf("The list of search path of ulc-file of -L-option is as follows.\n");
	printf("1. WHERE_UCS_ARE:     An environment variable of uls.\n\n");
	printf("2. ULS_CONFIG_DIR:    <instdir>/share, <instdir> is the installation directory\n");
	printf("                      that specified by 'configure --prefix <instdir>'.\n\n");
	printf("3. ULS_CONFIG_DFLDIR: A hard-coded list of dirs.\n\n");
	printf("         /usr/local/share:/usr/local/lib/share:/usr/lib/share   in Linux\n");
	printf("         C:\\Program Files;C:\\Temp                             in Windowds\n");
	printf("\n");

	printf("If you append the suffix(.uls) to uls-file,\n");
	printf("ULS will first search for the current directory prior to above directories.\n\n");
	printf("\n\n");

	printf("  -L, --lang=<ulc-spec>           The lexical-spec of your language. You can omit the suffix '.ulc'.\n");
	printf("  -o, --output=<filepath>         specify the output filepath, a stream-file.\n");
	printf("  -t, --type=<output-type>        specify the format of output-file.\n");
	printf("  -C, --conglomerate=<list-file>  output the conglomerate file from the multiple input-files.\n");
	printf("  -f, --filter=<cmdline>          preproceses the input files, used with the -C-option.\n");
	printf("  -v, --verbose                   turns on verbose mode.\n");
	printf("  -V, --version                   print the version information.\n");
	printf("  -h, --help                      display this help and exit.\n");
	printf("\n");

	printf("For example, you may use %s as follows.\n\n", progname);

	printf("To read ULS-file,\n");
	printf("   %s -L sample.ulc input1.txt\n\n", progname);
	printf("   %s -L sample.ulc bin-le.uls\n\n", progname);
	printf("   %s -L sample.ulc -o a.txt bin-le.uls\n\n", progname);

	printf("To write ULS-file,\n");
	printf("   %s -L sample.ulc -t bin-le bin-le.uls\n\n", progname);
	printf("   %s -L sample.ulc -t bin-be bin-le.uls\n\n", progname);
	printf("\n");

	printf("To get the conglomerate ULS-file,\n");
	printf("   %s -t bin    -C ./a.list /package/home\n\n", progname);
	printf("   %s -t bin-le -C ./a.list -f 'gcc -E' ../package/home\n\n", progname);
	printf("\n");
}

static int
ulsstream_options(int opt, char* optarg)
{
	char fpath_buff[ULS_FILEPATH_MAX+1];
	char *lptr, *cmdl, *argv0;
	int   siz, stat = 1;

	switch (opt) {
	case 'o':
		output_file = optarg;
		break;

	case 'f':
		cmdl = skip_space(optarg);

		siz = uls_strlen(cmdl) + ULS_FILEPATH_MAX + 4;
		cmdline_filter = malloc(siz);
		uls_strcpy(cmdline_filter, cmdl);

		lptr = argv0 = skip_space(cmdline_filter);
		for ( ; *lptr!='\0'; lptr++) {
			 if (isspace(*lptr)) break;
		}

		if (*lptr != '\0') *lptr++ = '\0';

		if (!is_absolute_path(argv0) && uls_file_exist(argv0) > 0) {
			uls_getcwd(fpath_buff, sizeof(fpath_buff));
			uls_snprintf(cmdline_filter, siz, "%s/%s", fpath_buff, cmdl);
		} else {
			free(cmdline_filter);
			cmdline_filter = cmdl;
		}
		break;

	case 't':
		opt_write = 1;
		if (uls_streql(optarg, "bin")) {
#ifdef ULS_BIG_ENDIAN
			out_ftype = ULS_STREAM_BIN_BE;
#else
			out_ftype = ULS_STREAM_BIN_LE;
#endif
		} else if (uls_streql(optarg, "bin-le")) {
			out_ftype = ULS_STREAM_BIN_LE;
		} else if (uls_streql(optarg, "bin-be")) {
			out_ftype = ULS_STREAM_BIN_BE;
		} else {
			err_log("%s: invalid option value for the option --type", optarg);
			stat = -1;
		}
		break;

	case 'L':
		config_name = optarg;
		break;

	case 'C':
		opt_write = 1;
		filelist = optarg;
		break;

	case 'n':
		tag_name = optarg;
		break;

	case 'z':
		opt_want_eof = 1;
		break;

	case 'x':
		opt_no_numbering = 1;
		break;

	case 'h':
		usage();
		stat = 0;
		break;

	case 'v':
		++opt_verbose;
		break;

	case 'V':
		printf("%s %s, written by %s,\n\tis provided under %s.\n",
			progname, ULF_GEN_PROGVER, ULS_AUTHOR, ULS_LICENSE_NAME);
		stat = 0;
		break;

	default:
		err_log("undefined option -%c", opt);
		stat = -1;
		break;
	}

	return stat;
}

static int
parse_options(int argc, char* argv[])
{
#ifdef HAVE_GETOPT_H
	int  rc, opt, longindex;
#endif
	int   i0;

#ifdef ULS_BIG_ENDIAN
		out_ftype = ULS_STREAM_BIN_BE;
#else
		out_ftype = ULS_STREAM_BIN_LE;
#endif

#ifdef HAVE_GETOPT_H
	while ((opt=getopt_long(argc, argv, "o:t:L:f:C:n:zxvVh", longopts, &longindex)) != -1) {
		if ((rc=ulsstream_options(opt, optarg)) <= 0) {
			return rc;
		}
	}
	i0 = optind;
#else
	if ((i0=uls_getopts(argc, argv, "o:t:L:f:C:n:zxvVh", ulsstream_options)) <= 0) {
		return i0;
	}
#endif

	if (opt_write && output_file == NULL) {
		output_file = "a.uls";
	}

	if (filelist != NULL) {
		if (i0 >= argc) {
			err_log("Specify the target-directory to apply the paths in %s", filelist);
			return -1;
		}

		target_dir = argv[i0++];
	}

	return i0;
}

int
guess_specname(char* fpath, char* specname)
{
	uls_stream_t in_hdr;
	int ftype;

	if (fpath == NULL || uls_open_stream_file(fpath, &in_hdr) < 0) {
		return -1;
	}

	uls_strcpy(specname, in_hdr.specname);
	ftype = in_hdr.filetype;
	uls_close_stream(&in_hdr);

	return ftype;
}

int
guess_specname_from_inputfiles(char* specname)
{
	char  linebuff[1024], *lptr, *fpath = NULL;
	FILE  *fp_list;
	int  len, stat = 0;

	if ((fp_list=fopen(filelist, "r")) == NULL) {
		err_log("%s: fail to read '%s'", __FUNCTION__, filelist);
		return -1;
	}

	if (uls_chdir(target_dir) < 0) {
		err_log("%s: fail to chdir %s", __FUNCTION__, target_dir);
		fclose(fp_list);
		return -1;
	}

	while (fgets(linebuff, sizeof(linebuff), fp_list) != NULL) {
		if ((len=strlen(linebuff))>0 && linebuff[len-1]=='\n') {
			linebuff[--len] = '\0';
		}

		lptr = skip_space(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		len -= (int) (lptr - linebuff);
		fpath = lptr; // the first file in the list
		break;
	}

	if (fpath == NULL) stat = -1;
	else stat = guess_specname(fpath, specname);

	fclose(fp_list);
	uls_chdir(home_dir);

	return stat;
}

int
read_stream_file(uls_lex_t* uls, char* fpath, uls_stream_t* hdr, FILE* fp_out)
{
	int flags = 0;

	if (opt_want_eof)
		flags |= ULS_WANT_EOF_TOK;

	if (uls_push_file_hdr(uls, hdr, flags) < 0) {
		err_log("incorrect format?, '%s'", fpath);
		return -1;
	}

	while (1) {
		uls_gettok(uls);

		if (uls_token_id(uls) == uls->toknum_ERR) {
			err_log("incorrect format?, '%s'", fpath);
			return -1;
		}

		if (uls_token_id(uls) != uls->toknum_EOF || opt_want_eof) {
			uls_fprintf(fp_out, "\t[%5d]   %s\n", uls_token_id(uls), uls_lexeme_text(uls));
		}

		if (uls_token_id(uls) == uls->toknum_EOI) break;
	}

	return 0;
}

int
dump_input_file(char* fpath, FILE *fp_out)
{
	uls_lex_t *sample_lex;
	char     *ulc_name;
	uls_stream_t in_hdr;
	int fd, stat=0;

	if ((fd=uls_open_stream_file(fpath, &in_hdr)) < 0) {
		err_log("can't read %s", fpath);
		return -1;
	}

	if (in_hdr.filetype == ULS_STREAM_RAW) {
		if (config_name == NULL) {
			err_log("don't know how to read %s! Specify c-option", fpath);
			uls_close_stream(&in_hdr);
			return -1;
		}
		ulc_name = config_name;
	} else {
		if (config_name == NULL) {
			ulc_name = in_hdr.specname;
		} else {
			ulc_name = config_name;
		}
	}

	if (opt_verbose)
		err_log("Loading uls config file %s, ...", ulc_name);

	if ((sample_lex=uls_create(ulc_name)) == NULL) {
		err_log("can't create a uls-object for lexical analysis.");
		return -1;
	}

	stat = read_stream_file(sample_lex, fpath, &in_hdr, fp_out);
	uls_close_stream(&in_hdr);

	uls_destroy(sample_lex);

	return stat;
}

static int
uls_start_stream_filepath_bin(uls_stream_t *hdr, char* fpath)
{
	uls_stream_t in_hdr;
	int flags = 0, stat = 0;

	if (opt_want_eof)
		flags |= ULS_WANT_EOF_TOK;

	if (uls_open_stream_file(fpath, &in_hdr) < 0 ||
		in_hdr.filetype == ULS_STREAM_RAW) {
		err_log("%s: can't read %s or file-type mismatched(ftype=%d)!",
			__FUNCTION__, fpath, in_hdr.filetype);
		return -1;
	}

	if (uls_start_stream(hdr, &in_hdr, flags) < 0) {
		err_log("%s: fail to uls-streaming to %d", fpath);
		stat = -1;
	}

	uls_close_stream(&in_hdr);

	return stat;
}

int
write_stream_file(uls_lex_t* uls,
	uls_stream_t* hdr, char* out_fpath)
{
	uls_stream_t  out_hdr;
	int fd_out, mode;
	int flags = 0;
	char tmpfile[1024];

	if (opt_want_eof)
		flags |= ULS_WANT_EOF_TOK;

	if (out_fpath == NULL) {
		fd_out = 1;
	} else if ((fd_out=uls_mktemp(tmpfile, sizeof(tmpfile))) < 0) {
		err_log(" file(for writing) open error");
		return -1;
	}

	mode = out_ftype;
	if (!opt_no_numbering) mode |= ULS_FL_LINE_NUMBERING;

	if (uls_create_stream(&out_hdr, uls, fd_out, mode, tag_name) < 0) {
		err_log("can't set uls-stream to %d", fd_out);
		return -1;
	}

	if (uls_start_stream(&out_hdr, hdr, flags) < 0) {
		err_log("fail to uls-streaming to %d", fd_out);
		return -1;
	}

	uls_close_stream(&out_hdr);

	if (out_fpath != NULL) {
		uls_close(fd_out);
		if (uls_movefile(tmpfile, out_fpath) < 0)
			return -1;
	}

	return 0;
}

int
write_uls_file(char* fpath)
{
	uls_lex_t *sample_lex;
	uls_stream_t in_hdr;
	int fd, stat=0;

	if ((fd=uls_open_stream_file(fpath, &in_hdr)) < 0) {
		err_log("can't read %s", fpath);
		return -1;
	}

	strncpy(in_hdr.tag, fpath, ULS_FILEPATH_MAX);
	in_hdr.tag[ULS_FILEPATH_MAX] = '\0';

	if (config_name == NULL) {
		if (in_hdr.filetype == ULS_STREAM_RAW) {
			err_log("don't know how to read %s! Specify L-option", fpath);
			uls_close_stream(&in_hdr);
			return -1;
		}
		config_name = in_hdr.specname;
	}

	if (opt_verbose)
		err_log("Loading uls config file %s, ...", config_name);

	if ((sample_lex=uls_create(config_name)) == NULL) {
		err_log("can't create a uls-object for lexical analysis.");
		uls_close_stream(&in_hdr);
		return -1;
	}

	if ((stat=write_stream_file(sample_lex, &in_hdr, output_file)) < 0) {
		err_log("fail to redirect the token-stream!");
	} else {
		err_log("Writing to '%s', ...", output_file);
	}

	uls_close_stream(&in_hdr);
	uls_destroy(sample_lex);

	return stat;
}

static int
write_stream_body_from_bin(FILE* fp_list, uls_stream_t* out_hdr)
{
	char  linebuff[1024], *lptr, *fpath;
	int   len, stat = 0;

	while (fgets(linebuff, sizeof(linebuff), fp_list) != NULL) {
		if ((len=strlen(linebuff))>0 && linebuff[len-1]=='\n') {
			linebuff[--len] = '\0';
		}

		lptr = skip_space(linebuff);
		if (*lptr=='\0' || *lptr=='#') continue;

		len -= (int) (lptr - linebuff);
		fpath = lptr;

		if (opt_verbose)
			err_log("processing %s, ...", fpath);

		if (uls_start_stream_filepath_bin(out_hdr, fpath) < 0) {
			err_log("fail to uls-streaming %s, ...", fpath);
			--stat;
			break;
		}
	}

	return stat;
}

#ifndef WIN32
static int
write_stream_body_from_raw(int fd_list, uls_stream_t* out_hdr)
{
	uls_stream_t in_hdr;
	fdf_t  fdfilter;
	int  flags = 0, stat = 0;

	if (opt_want_eof)
		flags |= ULS_WANT_EOF_TOK;

	fdf_init(&fdfilter, fdf_iprovider_filelist, cmdline_filter);

	if (uls_open_stream_filter_fd(fd_list, &fdfilter, &in_hdr) < 0 ||
		in_hdr.filetype != ULS_STREAM_RAW) {
		err_log("%s: can't conjecture file type!", __FUNCTION__);
		fdf_deinit(&fdfilter);
		return -2;
	}

	if (uls_start_stream(out_hdr, &in_hdr, flags) < 0) {
		err_log("%s: fail to uls-streaming to %d", __FUNCTION__);
		stat = -1;
	}

	uls_close_stream_filter(&in_hdr, &fdfilter);
	fdf_deinit(&fdfilter);

	return stat;
}
#endif

int
proc_filelist(int ftype, uls_lex_t *sam_lex)
{
	int   fd_list, rc, stat=0;
	uls_stream_t out_hdr;
	FILE  *fp_list;
	int mode;

	char tmpfile[1024];
	int fd_out;

#ifdef WIN32
	if (ftype == ULS_STREAM_RAW) {
		err_log("Currently not supported to make conglomerate stream from the total (raw) sources.");
		return -1;
	}
#endif
	if (opt_verbose)
		err_log("Loading uls config file %s, ...", config_name);

	// assert: output_file != NULL
	if ((fd_out=uls_mktemp(tmpfile, sizeof(tmpfile))) < 0) {
		err_log("%s: file(for writing) open error", output_file);
		return -1;
	}

	mode = out_ftype;
	if (!opt_no_numbering) mode |= ULS_FL_LINE_NUMBERING;

	if (uls_create_stream(&out_hdr, sam_lex, fd_out, mode, tag_name) < 0) {
		err_log("can't set uls-stream to %d", fd_out);
		return -1;
	}

	if ((fd_list = uls_open(filelist, O_RDONLY)) < 0 || uls_chdir(target_dir) < 0) {
		err_log("can't change to %s", target_dir);
		stat = -1;

	} else {
#ifndef WIN32
		if (ftype == ULS_STREAM_RAW) {
			rc = write_stream_body_from_raw(fd_list, &out_hdr);
		} else 
#endif
		{
			if ((fp_list=uls_fdopen(fd_list, "r")) == NULL) {
				err_log("%s: improper fd of list", __FUNCTION__);
				stat = -1;
			} else {
				rc = write_stream_body_from_bin(fp_list, &out_hdr);
				fclose(fp_list); // fd_list closed!
				fd_list = -1;
			}
		}

		if (rc < 0) {
			stat = -1;
		}
	}

	uls_chdir(home_dir);

	uls_close(fd_list);
	uls_close_stream(&out_hdr);

	if (stat == 0)
		err_log("Writing to '%s', ...", output_file);
	uls_close(fd_out);

	if (uls_movefile(tmpfile, output_file) < 0) {
		err_log("%s: can't move file %s into %s", tmpfile, output_file);
		return -1;
	}

	return stat;
}

int
main(int argc, char* argv[])
{
	int i0, stat = 0;
	int i, ftype;
#ifdef WIN32DEBUG
	initialize_uls();
#endif
	uls_getcwd(home_dir, sizeof(home_dir));
	progname = THIS_PROGNAME;

	if ((i0=parse_options(argc, argv)) <= 0) {
		if (i0 < 0) err_log("Incorrect use of command options.");
		return i0;
	}

	if (filelist != NULL) {
		uls_lex_t *sam_lex;

		if (config_name == NULL) {
			if ((ftype=guess_specname_from_inputfiles(ulc_specname)) < 0) {
				err_log("Unknown spec-name, specify the ulc-file of input-files");
				return -1;
			}
			config_name = ulc_specname;
		}
		if (opt_verbose)
				err_log("The output file depends on the lex-spec '%s'\n", config_name);

		uls_chdir(home_dir);

		if ((sam_lex=uls_create(config_name)) == NULL) {
			err_log("can't create a uls-object for lexical analysis.");
			return -1;
		}

		stat = proc_filelist(ftype, sam_lex);
		uls_destroy(sam_lex);

		return stat;
	}

	if (i0 >= argc) {
		err_log("%s(%s), written by %s,\n\tis provided under %s.",
			progname, ULF_GEN_PROGVER, ULS_AUTHOR, ULS_LICENSE_NAME);
		err_log("Type '%s -h' for usage.", progname);
		return 0;
	}

	if (opt_write) {
		if (i0 < argc) {
			write_uls_file(argv[i0]);
		}

	} else {
		FILE *fp_out;

		if (output_file != NULL) {
			if ((fp_out=fopen(output_file, "w")) == NULL) {
				err_log("can't create %s", output_file);
			}
		} else {
			fp_out = stdout;
		}

		for (i=i0; i<argc; i++) {
			dump_input_file(argv[i], fp_out);
		}

		if (fp_out != stdout) fclose(fp_out);
	}

	return stat;
}
